/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.tencent.tws.assistant.widget;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

import libcore.icu.LocaleData;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.NumberKeyListener;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.LayoutInflater.Filter;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeProvider;
import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.tencent.tws.sharelib.R;

public class NumberPicker extends LinearLayout {
	private static String TAG = "NumberPicker";

	// 显示的Item数目 - 循环滚动
	private static final int SELECTOR_WHEEL_ITEM_COUNT = 5;

	// 长安的更新间隔
	private static final long DEFAULT_LONG_PRESS_UPDATE_INTERVAL = 300;

	// 默认的选中焦点Item[索引]
	private static final int SELECTOR_MIDDLE_ITEM_INDEX = SELECTOR_WHEEL_ITEM_COUNT / 2;

	/**
	 * The coefficient by which to adjust (divide) the max fling velocity.
	 */
	private static final int SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT = 8;

	/**
	 * The the duration for adjusting the selector wheel.
	 */
	private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800;

	/**
	 * The duration of scrolling while snapping to a given position.
	 */
	private static final int SNAP_SCROLL_DURATION = 300;

	/**
	 * The strength of fading in the top and bottom while drawing the selector.
	 */
	private static final float TOP_AND_BOTTOM_FADING_EDGE_STRENGTH = 0.9f;

	/**
	 * The default unscaled height of the selection divider.
	 */
	private static final int UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT = 2;

	/**
	 * The default unscaled distance between the selection dividers.
	 */
	private static final int UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE = 48;

	/**
	 * The resource id for the default layout.
	 */
	private static final int DEFAULT_LAYOUT_RESOURCE_ID = R.layout.number_picker;

	/**
	 * Constant for unspecified size.
	 */
	private static final int SIZE_UNSPECIFIED = -1;

	DisplayMetrics displayMetrics;

	/**
	 * Use a custom NumberPicker formatting callback to use two-digit minutes
	 * strings like "01". Keeping a static formatter etc. is the most efficient
	 * way to do this; it avoids creating temporary objects on every call to
	 * format().
	 */
	private static class TwoDigitFormatter implements NumberPicker.Formatter {
		final StringBuilder mBuilder = new StringBuilder();

		char mZeroDigit;
		java.util.Formatter mFmt;

		final Object[] mArgs = new Object[1];

		TwoDigitFormatter() {
			final Locale locale = Locale.getDefault();
			init(locale);
		}

		private void init(Locale locale) {
			mFmt = createFormatter(locale);
			mZeroDigit = getZeroDigit(locale);
		}

		public String format(int value) {
			final Locale currentLocale = Locale.getDefault();
			if (mZeroDigit != getZeroDigit(currentLocale)) {
				init(currentLocale);
			}
			mArgs[0] = value;
			mBuilder.delete(0, mBuilder.length());
			mFmt.format("%02d", mArgs);
			return mFmt.toString();
		}

		private static char getZeroDigit(Locale locale) {
			return LocaleData.get(locale).zeroDigit;
		}

		private java.util.Formatter createFormatter(Locale locale) {
			return new java.util.Formatter(mBuilder, locale);
		}
	}

	private static final TwoDigitFormatter sTwoDigitFormatter = new TwoDigitFormatter();

	/**
	 * @hide
	 */
	public static final Formatter getTwoDigitFormatter() {
		return sTwoDigitFormatter;
	}

	/**
	 * The increment button.
	 */
	private final ImageButton mIncrementButton;

	/**
	 * The decrement button.
	 */
	private final ImageButton mDecrementButton;

	/**
	 * The text for showing the current value.
	 */
	private final EditText mInputText;

	/**
	 * The distance between the two selection dividers.
	 */
	private final int mSelectionDividersDistance;

	/**
	 * The min height of this widget.
	 */
	private final int mMinHeight;

	/**
	 * The max height of this widget.
	 */
	private final int mMaxHeight;

	/**
	 * The max width of this widget.
	 */
	private final int mMinWidth;

	/**
	 * The max width of this widget.
	 */
	private int mMaxWidth;

	/**
	 * Flag whether to compute the max width.
	 */
	private final boolean mComputeMaxWidth;

	/**
	 * The height of the text.
	 */
	private final int mTextSize;

	/**
	 * The height of the gap between text elements if the selector wheel.
	 */
	private int mSelectorTextGapHeight;

	/**
	 * The values to be displayed instead the indices.
	 */
	private String[] mDisplayedValues;

	/**
	 * Lower value of the range of numbers allowed for the NumberPicker
	 */
	private int mMinValue;

	/**
	 * Upper value of the range of numbers allowed for the NumberPicker
	 */
	private int mMaxValue;

	/**
	 * Current value of this NumberPicker
	 */
	private int mValue;

	/**
	 * Listener to be notified upon current value change.
	 */
	private OnValueChangeListener mOnValueChangeListener;

	/**
	 * Listener to be notified upon scroll state change.
	 */
	private OnScrollListener mOnScrollListener;

	/**
	 * Formatter for for displaying the current value.
	 */
	private Formatter mFormatter;

	/**
	 * The speed for updating the value form long press.
	 */
	private long mLongPressUpdateInterval = DEFAULT_LONG_PRESS_UPDATE_INTERVAL;

	/**
	 * Cache for the string representation of selector indices.
	 */
	private final SparseArray<String> mSelectorIndexToStringCache = new SparseArray<String>();

	/**
	 * The selector indices whose value are show by the selector.
	 */
	private final int[] mSelectorIndices = new int[SELECTOR_WHEEL_ITEM_COUNT];

	/**
	 * The {@link Paint} for drawing the selector.
	 */
	private final Paint mSelectorWheelPaint;

	/**
	 * The {@link Drawable} for pressed virtual (increment/decrement) buttons.
	 */
	private final Drawable mVirtualButtonPressedDrawable;

	/**
	 * The height of a selector element (text + gap).
	 */
	private int mSelectorElementHeight;

	/**
	 * The initial offset of the scroll selector.
	 */
	private int mInitialScrollOffset = Integer.MIN_VALUE;

	/**
	 * The current offset of the scroll selector.
	 */
	private int mCurrentScrollOffset;

	/**
	 * The {@link TwsScroller} responsible for flinging the selector.
	 */
	private final TwsScroller mFlingScroller;

	/**
	 * The {@link TwsScroller} responsible for adjusting the selector.
	 */
	private final TwsScroller mAdjustScroller;

	/**
	 * The previous Y coordinate while scrolling the selector.
	 */
	private int mPreviousScrollerY;

	/**
	 * Handle to the reusable command for setting the input text selection.
	 */
	private SetSelectionCommand mSetSelectionCommand;

	/**
	 * Handle to the reusable command for changing the current value from long
	 * press by one.
	 */
	private ChangeCurrentByOneFromLongPressCommand mChangeCurrentByOneFromLongPressCommand;

	/**
	 * Command for beginning an edit of the current value via IME on long press.
	 */
	private BeginSoftInputOnLongPressCommand mBeginSoftInputOnLongPressCommand;

	/**
	 * The Y position of the last down event.
	 */
	private float mLastDownEventY;

	/**
	 * The time of the last down event.
	 */
	private long mLastDownEventTime;

	/**
	 * The Y position of the last down or move event.
	 */
	private float mLastDownOrMoveEventY;

	/**
	 * Determines speed during touch scrolling.
	 */
	private VelocityTracker mVelocityTracker;

	/**
	 * @see ViewConfiguration#getScaledTouchSlop()
	 */
	private int mTouchSlop;

	/**
	 * @see ViewConfiguration#getScaledMinimumFlingVelocity()
	 */
	private int mMinimumFlingVelocity;

	/**
	 * @see ViewConfiguration#getScaledMaximumFlingVelocity()
	 */
	private int mMaximumFlingVelocity;

	/**
	 * Flag whether the selector should wrap around.
	 */
	private boolean mWrapSelectorWheel;

	/**
	 * The back ground color used to optimize scroller fading.
	 */
	private final int mSolidColor;

	/**
	 * Flag whether this widget has a selector wheel.
	 */
	private final boolean mHasSelectorWheel;

	/**
	 * Divider for showing item to be selected while scrolling
	 */
	private final Drawable mSelectionDivider;

	/**
	 * The height of the selection divider.
	 */
	private final int mSelectionDividerHeight;

	/**
	 * The current scroll state of the number picker.
	 */
	private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;

	/**
	 * Flag whether to ignore move events - we ignore such when we show in IME
	 * to prevent the content from scrolling.
	 */
	private boolean mIgnoreMoveEvents;

	/**
	 * Flag whether to perform a click on tap.
	 */
	private boolean mPerformClickOnTap;

	/**
	 * The top of the top selection divider.
	 */
	private int mTopSelectionDividerTop;

	/**
	 * The bottom of the bottom selection divider.
	 */
	private int mBottomSelectionDividerBottom;

	/**
	 * The virtual id of the last hovered child.
	 */
	private int mLastHoveredChildVirtualViewId;

	/**
	 * Whether the increment virtual button is pressed.
	 */
	private boolean mIncrementVirtualButtonPressed;

	/**
	 * Whether the decrement virtual button is pressed.
	 */
	private boolean mDecrementVirtualButtonPressed;

	/**
	 * Provider to report to clients the semantic structure of this widget.
	 */
	private AccessibilityNodeProviderImpl mAccessibilityNodeProvider;

	/**
	 * Helper class for managing pressed state of the virtual buttons.
	 */
	private final PressedStateHelper mPressedStateHelper;

	/**
	 * The keycode of the last handled DPAD down event.
	 */
	private int mLastHandledDownDpadKeyCode = -1;

	// tws-start label::2014-8-6
	private int mLabelLocation;
	public static final int LABEL_LEFT = 0;
	public static final int LABEL_RIGHT = 1;
	private String mLabel;
	private final Paint mLabelPaint;
	private boolean mChangeValueByOneEable = true;
	private boolean mChangeCurrentByOneFromLongPressEable = false;
	private int mNormalTextSize;
	private static final int UNSCALED_DEFAULT_NORMAL_TEXT_SIZE = 12;
	private int mNormalTextColor;
	private float mTextScale;
	private boolean mSlowScroller = false;

	public static final int ALIGN_CENTER_TYPE = 0;
	public static final int ALIGN_LEFT_TYPE = 1;
	public static final int ALIGN_RIGHT_TYPE = 2;
	private int mAlignType = ALIGN_CENTER_TYPE;

	// tws-end label::2014-8-6

	/**
	 * Interface to listen for changes of the current value.
	 */
	public interface OnValueChangeListener {

		/**
		 * Called upon a change of the current value.
		 * 
		 * @param picker
		 *            The NumberPicker associated with this listener.
		 * @param oldVal
		 *            The previous value.
		 * @param newVal
		 *            The new value.
		 */
		void onValueChange(NumberPicker picker, int oldVal, int newVal);
	}

	/**
	 * Interface to listen for the picker scroll state.
	 */
	public interface OnScrollListener {

		/**
		 * The view is not scrolling.
		 */
		public static int SCROLL_STATE_IDLE = 0;

		/**
		 * The user is scrolling using touch, and his finger is still on the
		 * screen.
		 */
		public static int SCROLL_STATE_TOUCH_SCROLL = 1;

		/**
		 * The user had previously been scrolling using touch and performed a
		 * fling.
		 */
		public static int SCROLL_STATE_FLING = 2;

		/**
		 * Callback invoked while the number picker scroll state has changed.
		 * 
		 * @param view
		 *            The view whose scroll state is being reported.
		 * @param scrollState
		 *            The current scroll state. One of
		 *            {@link #SCROLL_STATE_IDLE},
		 *            {@link #SCROLL_STATE_TOUCH_SCROLL} or
		 *            {@link #SCROLL_STATE_IDLE}.
		 */
		public void onScrollStateChange(NumberPicker view, int scrollState);
	}

	/**
	 * Interface used to format current value into a string for presentation.
	 */
	public interface Formatter {

		/**
		 * Formats a string representation of the current value.
		 * 
		 * @param value
		 *            The currently selected value.
		 * @return A formatted string representation.
		 */
		public String format(int value);
	}

	/**
	 * Create a new number picker.
	 * 
	 * @param context
	 *            The application environment.
	 */
	public NumberPicker(Context context) {
		this(context, null);
	}

	/**
	 * Create a new number picker.
	 * 
	 * @param context
	 *            The application environment.
	 * @param attrs
	 *            A collection of attributes.
	 */
	public NumberPicker(Context context, AttributeSet attrs) {
		this(context, attrs, R.attr.numberPickerStyle);
	}

	/**
	 * Create a new number picker
	 * 
	 * @param context
	 *            the application environment.
	 * @param attrs
	 *            a collection of attributes.
	 * @param defStyle
	 *            The default style to apply to this view.
	 */
	public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		// process style attributes
		TypedArray attributesArray = context.obtainStyledAttributes(attrs, R.styleable.NumberPicker, defStyle, 0);
		final int layoutResId = attributesArray.getResourceId(R.styleable.NumberPicker_layout,
				DEFAULT_LAYOUT_RESOURCE_ID);

		// tws-start label::2014-8-7
		// mHasSelectorWheel = (layoutResId != DEFAULT_LAYOUT_RESOURCE_ID);
		mHasSelectorWheel = true;
		final int defNormalTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
				UNSCALED_DEFAULT_NORMAL_TEXT_SIZE, getResources().getDisplayMetrics());
		mNormalTextSize = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_normalTextSize,
				defNormalTextSize);
		mNormalTextColor = attributesArray.getColor(R.styleable.NumberPicker_normalTextColor,
				getResources().getColor(R.color.tws_black));
		// tws-end label::2014-8-7

		mSolidColor = attributesArray.getColor(R.styleable.NumberPicker_solidColor, 0);

		mSelectionDivider = attributesArray.getDrawable(R.styleable.NumberPicker_selectionDivider);

		final int defSelectionDividerHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
				UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT, getResources().getDisplayMetrics());
		mSelectionDividerHeight = attributesArray.getDimensionPixelSize(
				R.styleable.NumberPicker_selectionDividerHeight, defSelectionDividerHeight);

		final int defSelectionDividerDistance = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
				UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE, getResources().getDisplayMetrics());
		mSelectionDividersDistance = attributesArray.getDimensionPixelSize(
				R.styleable.NumberPicker_selectionDividersDistance, defSelectionDividerDistance);

		mMinHeight = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_minHeight, SIZE_UNSPECIFIED);

		mMaxHeight = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_maxHeight, SIZE_UNSPECIFIED);
		if (mMinHeight != SIZE_UNSPECIFIED && mMaxHeight != SIZE_UNSPECIFIED && mMinHeight > mMaxHeight) {
			throw new IllegalArgumentException("minHeight > maxHeight");
		}

		mMinWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_minWidth, SIZE_UNSPECIFIED);

		mMaxWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_maxWidth, SIZE_UNSPECIFIED);
		if (mMinWidth != SIZE_UNSPECIFIED && mMaxWidth != SIZE_UNSPECIFIED && mMinWidth > mMaxWidth) {
			throw new IllegalArgumentException("minWidth > maxWidth");
		}

		mComputeMaxWidth = (mMaxWidth == SIZE_UNSPECIFIED);

		mVirtualButtonPressedDrawable = attributesArray
				.getDrawable(R.styleable.NumberPicker_virtualButtonPressedDrawable);

		attributesArray.recycle();

		mPressedStateHelper = new PressedStateHelper();

		// By default Linearlayout that we extend is not drawn. This is
		// its draw() method is not called but dispatchDraw() is called
		// directly (see ViewGroup.drawChild()). However, this class uses
		// the fading edge effect implemented by View and we need our
		// draw() method to be called. Therefore, we declare we will draw.
		setWillNotDraw(!mHasSelectorWheel);

		LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		inflater.inflate(layoutResId, this, true);

		OnClickListener onClickListener = new OnClickListener() {
			public void onClick(View v) {
				hideSoftInput();
				mInputText.clearFocus();
				if (v.getId() == R.id.increment) {
					changeValueByOne(true);
				} else {
					changeValueByOne(false);
				}
			}
		};

		OnLongClickListener onLongClickListener = new OnLongClickListener() {
			public boolean onLongClick(View v) {
				hideSoftInput();
				mInputText.clearFocus();
				if (v.getId() == R.id.increment) {
					postChangeCurrentByOneFromLongPress(true, 0);
				} else {
					postChangeCurrentByOneFromLongPress(false, 0);
				}
				return true;
			}
		};
		displayMetrics = getResources().getDisplayMetrics();

		// increment button
		if (!mHasSelectorWheel) {
			mIncrementButton = (ImageButton) findViewById(R.id.increment);
			mIncrementButton.setOnClickListener(onClickListener);
			mIncrementButton.setOnLongClickListener(onLongClickListener);
		} else {
			mIncrementButton = null;
		}

		// decrement button
		if (!mHasSelectorWheel) {
			mDecrementButton = (ImageButton) findViewById(R.id.decrement);
			mDecrementButton.setOnClickListener(onClickListener);
			mDecrementButton.setOnLongClickListener(onLongClickListener);
		} else {
			mDecrementButton = null;
		}

		// input text
		mInputText = (EditText) findViewById(R.id.numberpicker_input);
		mInputText.setVisibility(View.INVISIBLE);
		mInputText.setOnFocusChangeListener(new OnFocusChangeListener() {
			public void onFocusChange(View v, boolean hasFocus) {
				if (hasFocus) {
					mInputText.selectAll();
				} else {
					mInputText.setSelection(0, 0);
					validateInputTextView(v);
				}
			}
		});
		// tws-start label::2014-8-6
		// mInputText.setFilters(new InputFilter[] {
		// //new InputTextFilter()
		// new TwsInputTextFilter()
		// });
		mInputText.setClickable(false);
		mInputText.setEnabled(false);
		mInputText.setFocusable(false);
		// tws-end label::2014-8-6

		mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
		mInputText.setImeOptions(EditorInfo.IME_ACTION_DONE);

		// initialize constants
		ViewConfiguration configuration = ViewConfiguration.get(context);
		mTouchSlop = configuration.getScaledTouchSlop();
		mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity();
		mMaximumFlingVelocity = configuration.getScaledMaximumFlingVelocity() / SELECTOR_MAX_FLING_VELOCITY_ADJUSTMENT;
		// tws-start number picker::2014-8-13
		// mTextSize = (int) mInputText.getTextSize();
		mTextScale = mInputText.getTextSize() / mNormalTextSize;
		mTextSize = mNormalTextSize;
		// tws-end number picker::2014-8-13

		// create the selector wheel paint
		Paint paint = new Paint();
		paint.setAntiAlias(true);
		switch (mAlignType) {
		case ALIGN_LEFT_TYPE:
			paint.setTextAlign(Align.LEFT);
			break;
		case ALIGN_RIGHT_TYPE:
			paint.setTextAlign(Align.CENTER);
			break;
		default:
			paint.setTextAlign(Align.CENTER);
			break;
		}
		// tws-start number picker::2014-8-13
		// paint.setTextSize(mTextSize);
		paint.setTextSize(mNormalTextSize);
		// tws-end number picker::2014-8-13
		paint.setTypeface(mInputText.getTypeface());
		ColorStateList colors = mInputText.getTextColors();
		int color = colors.getColorForState(ENABLED_STATE_SET, Color.WHITE);
		paint.setColor(color);
		mSelectorWheelPaint = paint;

		// create the fling and adjust scrollers
		mFlingScroller = new TwsScroller(getContext(), null, true);
		mAdjustScroller = new TwsScroller(getContext(), new DecelerateInterpolator(2.5f));

		updateInputTextView();

		// If not explicitly specified this view is important for accessibility.
		if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
			setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
		}
		// tws-start label::2014-8-6
		mLabelPaint = new Paint();
		mLabelPaint.setAntiAlias(true);
		mLabelPaint.setTextAlign(Align.CENTER);
		mLabelPaint.setTextSize((int) (mNormalTextSize * 0.75f));
		mLabelPaint.setTypeface(mInputText.getTypeface());
		ColorStateList colorsLable = mInputText.getTextColors();
		int colorLable = colorsLable.getColorForState(ENABLED_STATE_SET, Color.WHITE);
		mLabelPaint.setColor(colorLable);
		// tws-end label::2014-8-6
	}

	public void setTextAlignType(int alignType) {
		if (mAlignType == alignType)
			return;

		mAlignType = alignType;
		switch (mAlignType) {
		case ALIGN_LEFT_TYPE:
			mSelectorWheelPaint.setTextAlign(Align.LEFT);
//			setBackgroundColor(R.color.tws_black_alpha_mini);
			break;
		case ALIGN_RIGHT_TYPE:
			mSelectorWheelPaint.setTextAlign(Align.RIGHT);
//			setBackgroundColor(R.color.tws_black_alpha_micro);
			break;
		default:
			mSelectorWheelPaint.setTextAlign(Align.CENTER);
//			setBackgroundColor(R.color.tws_light);
			break;
		}
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
		if (!mHasSelectorWheel) {
			super.onLayout(changed, left, top, right, bottom);
			return;
		}
		final int msrdWdth = getMeasuredWidth();
		final int msrdHght = getMeasuredHeight();

		// Input text centered horizontally.
		final int inptTxtMsrdWdth = mInputText.getMeasuredWidth();
		final int inptTxtMsrdHght = mInputText.getMeasuredHeight();
		final int inptTxtLeft = (msrdWdth - inptTxtMsrdWdth) / 2;
		final int inptTxtTop = (msrdHght - inptTxtMsrdHght) / 2;
		final int inptTxtRight = inptTxtLeft + inptTxtMsrdWdth;
		final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght;
		mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom);

		if (changed) {
			// need to do all this when we know our size
			initializeSelectorWheel();
			initializeFadingEdges();
			mTopSelectionDividerTop = (getHeight() - mSelectionDividersDistance) / 2 - mSelectionDividerHeight;
			mBottomSelectionDividerBottom = mTopSelectionDividerTop + 2 * mSelectionDividerHeight
					+ mSelectionDividersDistance;
		}
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		if (!mHasSelectorWheel) {
			super.onMeasure(widthMeasureSpec, heightMeasureSpec);
			return;
		}
		// Try greedily to fit the max width and height.
		final int newWidthMeasureSpec = makeMeasureSpec(widthMeasureSpec, mMaxWidth);
		final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMaxHeight);
		super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec);
		// Flag if we are measured with width or height less than the respective
		// min.
		final int widthSize = resolveSizeAndStateRespectingMinSize(mMinWidth, getMeasuredWidth(), widthMeasureSpec);
		final int heightSize = resolveSizeAndStateRespectingMinSize(mMinHeight, getMeasuredHeight(), heightMeasureSpec);
		setMeasuredDimension(widthSize, heightSize);
	}

	/**
	 * Move to the final position of a scroller. Ensures to force finish the
	 * scroller and if it is not at its final position a scroll of the selector
	 * wheel is performed to fast forward to the final position.
	 * 
	 * @param scroller
	 *            The scroller to whose final position to get.
	 * @return True of the a move was performed, i.e. the scroller was not in
	 *         final position.
	 */
	private boolean moveToFinalScrollerPosition(TwsScroller scroller) {
		scroller.forceFinished(true);
		int amountToScroll = scroller.getFinalY() - scroller.getCurrY();
		int futureScrollOffset = (mCurrentScrollOffset + amountToScroll) % mSelectorElementHeight;
		int overshootAdjustment = mInitialScrollOffset - futureScrollOffset;
		if (overshootAdjustment != 0) {
			if (Math.abs(overshootAdjustment) > mSelectorElementHeight / 2) {
				if (overshootAdjustment > 0) {
					overshootAdjustment -= mSelectorElementHeight;
				} else {
					overshootAdjustment += mSelectorElementHeight;
				}
			}
			amountToScroll += overshootAdjustment;
			scrollBy(0, amountToScroll);
			return true;
		}
		return false;
	}

	@Override
	public boolean onInterceptTouchEvent(MotionEvent event) {
		if (!mHasSelectorWheel || !isEnabled()) {
			return false;
		}
		final int action = event.getActionMasked();
		switch (action) {
		case MotionEvent.ACTION_DOWN: {
			removeAllCallbacks();
			mInputText.setVisibility(View.INVISIBLE);
			mLastDownOrMoveEventY = mLastDownEventY = event.getY();
			mLastDownEventTime = event.getEventTime();
			mIgnoreMoveEvents = false;
			mPerformClickOnTap = false;
			// Handle pressed state before any state change.
			if (mLastDownEventY < mTopSelectionDividerTop) {
				if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
					mPressedStateHelper.buttonPressDelayed(PressedStateHelper.BUTTON_DECREMENT);
				}
			} else if (mLastDownEventY > mBottomSelectionDividerBottom) {
				if (mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
					mPressedStateHelper.buttonPressDelayed(PressedStateHelper.BUTTON_INCREMENT);
				}
			}
			// Make sure we support flinging inside scrollables.
			getParent().requestDisallowInterceptTouchEvent(true);
			if (!mFlingScroller.isFinished()) {
				mFlingScroller.forceFinished(true);
				mAdjustScroller.forceFinished(true);
				onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
			} else if (!mAdjustScroller.isFinished()) {
				mFlingScroller.forceFinished(true);
				mAdjustScroller.forceFinished(true);
			} else if (mLastDownEventY < mTopSelectionDividerTop) {
				hideSoftInput();
				postChangeCurrentByOneFromLongPress(false, ViewConfiguration.getLongPressTimeout());
			} else if (mLastDownEventY > mBottomSelectionDividerBottom) {
				hideSoftInput();
				postChangeCurrentByOneFromLongPress(true, ViewConfiguration.getLongPressTimeout());
			} else {
				mPerformClickOnTap = true;
				postBeginSoftInputOnLongPressCommand();
			}
			return true;
		}
		}
		return false;
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (!isEnabled() || !mHasSelectorWheel) {
			return false;
		}
		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		}
		mVelocityTracker.addMovement(event);
		int action = event.getActionMasked();
		switch (action) {
		case MotionEvent.ACTION_MOVE: {
			if (mIgnoreMoveEvents) {
				break;
			}
			float currentMoveY = event.getY();
			if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
				int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY);
				if (deltaDownY > mTouchSlop) {
					removeAllCallbacks();
					onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
				}
			} else {
				int deltaMoveY = (int) ((currentMoveY - mLastDownOrMoveEventY));
				scrollBy(0, deltaMoveY);
				invalidate();
			}
			mLastDownOrMoveEventY = currentMoveY;
		}
			break;
		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP: {
			removeBeginSoftInputCommand();
			removeChangeCurrentByOneFromLongPress();
			mPressedStateHelper.cancel();
			VelocityTracker velocityTracker = mVelocityTracker;
			velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity);
			int initialVelocity = (int) velocityTracker.getYVelocity();
			if (Math.abs(initialVelocity) > mMinimumFlingVelocity) {
				fling(initialVelocity);
				onScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
			} else {
				int eventY = (int) event.getY();
				int deltaMoveY = (int) Math.abs(eventY - mLastDownEventY);
				long deltaTime = event.getEventTime() - mLastDownEventTime;
				if (deltaMoveY <= mTouchSlop && deltaTime < ViewConfiguration.getTapTimeout()) {
					if (mPerformClickOnTap) {
						mPerformClickOnTap = false;
						performClick();
					} else {
						int selectorIndexOffset = (eventY / mSelectorElementHeight) - SELECTOR_MIDDLE_ITEM_INDEX;
						if (selectorIndexOffset >= 0) {
							selectorIndexOffset = Math.min(selectorIndexOffset, SELECTOR_WHEEL_ITEM_COUNT / 2);
						} else {
							selectorIndexOffset = -Math.min(-selectorIndexOffset, SELECTOR_WHEEL_ITEM_COUNT / 2);
						}

						changeValueByOffset(selectorIndexOffset);
						mPressedStateHelper.buttonTapped(PressedStateHelper.BUTTON_INCREMENT);
					}
				} else {
					mFlingScroller.forceFinished(true);
					ensureScrollWheelAdjusted();
				}
				onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
			}
			mVelocityTracker.recycle();
			mVelocityTracker = null;
		}
			break;
		}
		return true;
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		final int action = event.getActionMasked();
		switch (action) {
		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			removeAllCallbacks();
			break;
		}
		return super.dispatchTouchEvent(event);
	}

	@Override
	public boolean dispatchKeyEvent(KeyEvent event) {
		final int keyCode = event.getKeyCode();
		switch (keyCode) {
		case KeyEvent.KEYCODE_DPAD_CENTER:
		case KeyEvent.KEYCODE_ENTER:
			removeAllCallbacks();
			break;
		case KeyEvent.KEYCODE_DPAD_DOWN:
		case KeyEvent.KEYCODE_DPAD_UP:
			if (!mHasSelectorWheel) {
				break;
			}
			switch (event.getAction()) {
			case KeyEvent.ACTION_DOWN:
				if (mWrapSelectorWheel
						|| ((keyCode == KeyEvent.KEYCODE_DPAD_DOWN) ? getValue() < getMaxValue()
								: getValue() > getMinValue())) {
					requestFocus();
					mLastHandledDownDpadKeyCode = keyCode;
					removeAllCallbacks();
					if (mFlingScroller.isFinished()) {
						changeValueByOne(keyCode == KeyEvent.KEYCODE_DPAD_DOWN);
					}
					return true;
				}
				break;
			case KeyEvent.ACTION_UP:
				if (mLastHandledDownDpadKeyCode == keyCode) {
					mLastHandledDownDpadKeyCode = -1;
					return true;
				}
				break;
			}
		}
		return super.dispatchKeyEvent(event);
	}

	@Override
	public boolean dispatchTrackballEvent(MotionEvent event) {
		final int action = event.getActionMasked();
		switch (action) {
		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			removeAllCallbacks();
			break;
		}
		return super.dispatchTrackballEvent(event);
	}

	@Override
	protected boolean dispatchHoverEvent(MotionEvent event) {
		if (!mHasSelectorWheel) {
			return super.dispatchHoverEvent(event);
		}
		if (AccessibilityManager.getInstance(mContext).isEnabled()) {
			final int eventY = (int) event.getY();
			final int hoveredVirtualViewId;
			if (eventY < mTopSelectionDividerTop) {
				hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_DECREMENT;
			} else if (eventY > mBottomSelectionDividerBottom) {
				hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INCREMENT;
			} else {
				hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INPUT;
			}
			final int action = event.getActionMasked();
			AccessibilityNodeProviderImpl provider = (AccessibilityNodeProviderImpl) getAccessibilityNodeProvider();
			switch (action) {
			case MotionEvent.ACTION_HOVER_ENTER: {
				provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
						AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
				mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
				provider.performAction(hoveredVirtualViewId, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
			}
				break;
			case MotionEvent.ACTION_HOVER_MOVE: {
				if (mLastHoveredChildVirtualViewId != hoveredVirtualViewId
						&& mLastHoveredChildVirtualViewId != View.NO_ID) {
					provider.sendAccessibilityEventForVirtualView(mLastHoveredChildVirtualViewId,
							AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
					provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
							AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
					mLastHoveredChildVirtualViewId = hoveredVirtualViewId;
					provider.performAction(hoveredVirtualViewId, AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
				}
			}
				break;
			case MotionEvent.ACTION_HOVER_EXIT: {
				provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId,
						AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
				mLastHoveredChildVirtualViewId = View.NO_ID;
			}
				break;
			}
		}
		return false;
	}

	@Override
	public void computeScroll() {
		TwsScroller scroller = mFlingScroller;
		if (scroller.isFinished()) {
			scroller = mAdjustScroller;
			if (scroller.isFinished()) {
				return;
			}
		}
		scroller.computeScrollOffset();
		int currentScrollerY = scroller.getCurrY();
		if (mPreviousScrollerY == 0) {
			mPreviousScrollerY = scroller.getStartY();
		}
		scrollBy(0, currentScrollerY - mPreviousScrollerY);
		mPreviousScrollerY = currentScrollerY;
		if (scroller.isFinished()) {
			onScrollerFinished(scroller);
		} else {
			invalidate();
		}
	}

	@Override
	public void setEnabled(boolean enabled) {
		super.setEnabled(enabled);
		if (!mHasSelectorWheel) {
			mIncrementButton.setEnabled(enabled);
		}
		if (!mHasSelectorWheel) {
			mDecrementButton.setEnabled(enabled);
		}
		mInputText.setEnabled(enabled);
	}

	@Override
	public void scrollBy(int x, int y) {
		int[] selectorIndices = mSelectorIndices;
		if (!mWrapSelectorWheel && y > 0 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue
				&& mCurrentScrollOffset >= (mInitialScrollOffset + mSelectorElementHeight / 4)) {
			mCurrentScrollOffset = (mInitialScrollOffset + mSelectorElementHeight / 4);
			// mCurrentScrollOffset = mInitialScrollOffset;
			return;
		}
		if (!mWrapSelectorWheel && y < 0 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue
				&& mCurrentScrollOffset <= (mInitialScrollOffset - mSelectorElementHeight / 4)) {
			mCurrentScrollOffset = (mInitialScrollOffset - mSelectorElementHeight / 4);
			// mCurrentScrollOffset = mInitialScrollOffset;
			return;
		}
		mCurrentScrollOffset += y;
		if (mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapHeight) {
			while (mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapHeight) {
				mCurrentScrollOffset -= mSelectorElementHeight;
				if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) {
					// mCurrentScrollOffset = mInitialScrollOffset;
					mCurrentScrollOffset = (mInitialScrollOffset + mSelectorElementHeight / 4);
				}
			}

			decrementSelectorIndices(selectorIndices);
			setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true);

			if (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapHeight) {
				Log.e(TAG, "mCurrentScrollOffset=" + mCurrentScrollOffset + " mInitialScrollOffset="
						+ mInitialScrollOffset + " mSelectorTextGapHeight=" + mSelectorTextGapHeight
						+ "mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapHeight");
			}
		} else if (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapHeight) {
			while (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapHeight) {
				mCurrentScrollOffset += mSelectorElementHeight;
				if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) {
					// mCurrentScrollOffset = mInitialScrollOffset;
					mCurrentScrollOffset = (mInitialScrollOffset - mSelectorElementHeight / 4);
				}
			}

			incrementSelectorIndices(selectorIndices);
			setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true);

			if (mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapHeight) {
				Log.e(TAG, "mCurrentScrollOffset=" + mCurrentScrollOffset + " mInitialScrollOffset="
						+ mInitialScrollOffset + " mSelectorTextGapHeight=" + mSelectorTextGapHeight
						+ "mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapHeight");
			}
		}
	}

	@Override
	protected int computeVerticalScrollOffset() {
		return mCurrentScrollOffset;
	}

	@Override
	protected int computeVerticalScrollRange() {
		return (mMaxValue - mMinValue + 1) * mSelectorElementHeight;
	}

	@Override
	protected int computeVerticalScrollExtent() {
		return getHeight();
	}

	@Override
	public int getSolidColor() {
		return mSolidColor;
	}

	/**
	 * Sets the listener to be notified on change of the current value.
	 * 
	 * @param onValueChangedListener
	 *            The listener.
	 */
	public void setOnValueChangedListener(OnValueChangeListener onValueChangedListener) {
		mOnValueChangeListener = onValueChangedListener;
	}

	/**
	 * Set listener to be notified for scroll state changes.
	 * 
	 * @param onScrollListener
	 *            The listener.
	 */
	public void setOnScrollListener(OnScrollListener onScrollListener) {
		mOnScrollListener = onScrollListener;
	}

	/**
	 * Set the formatter to be used for formatting the current value.
	 * <p>
	 * Note: If you have provided alternative values for the values this
	 * formatter is never invoked.
	 * </p>
	 * 
	 * @param formatter
	 *            The formatter object. If formatter is <code>null</code>,
	 *            {@link String#valueOf(int)} will be used.
	 * @see #setDisplayedValues(String[])
	 */
	public void setFormatter(Formatter formatter) {
		if (formatter == mFormatter) {
			return;
		}
		mFormatter = formatter;
		initializeSelectorWheelIndices();
		updateInputTextView();
	}

	/**
	 * Set the current value for the number picker.
	 * <p>
	 * If the argument is less than the {@link NumberPicker#getMinValue()} and
	 * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the
	 * current value is set to the {@link NumberPicker#getMinValue()} value.
	 * </p>
	 * <p>
	 * If the argument is less than the {@link NumberPicker#getMinValue()} and
	 * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the
	 * current value is set to the {@link NumberPicker#getMaxValue()} value.
	 * </p>
	 * <p>
	 * If the argument is less than the {@link NumberPicker#getMaxValue()} and
	 * {@link NumberPicker#getWrapSelectorWheel()} is <code>false</code> the
	 * current value is set to the {@link NumberPicker#getMaxValue()} value.
	 * </p>
	 * <p>
	 * If the argument is less than the {@link NumberPicker#getMaxValue()} and
	 * {@link NumberPicker#getWrapSelectorWheel()} is <code>true</code> the
	 * current value is set to the {@link NumberPicker#getMinValue()} value.
	 * </p>
	 * 
	 * @param value
	 *            The current value.
	 * @see #setWrapSelectorWheel(boolean)
	 * @see #setMinValue(int)
	 * @see #setMaxValue(int)
	 */
	public void setValue(int value) {
		setValueInternal(value, false);
	}

	@Override
	public boolean performClick() {
		if (!mHasSelectorWheel) {
			return super.performClick();
		} else if (!super.performClick()) {
			showSoftInput();
		}
		return true;
	}

	@Override
	public boolean performLongClick() {
		if (!mHasSelectorWheel) {
			return super.performLongClick();
		} else if (!super.performLongClick()) {
			showSoftInput();
			mIgnoreMoveEvents = true;
		}
		return true;
	}

	/**
	 * Shows the soft input for its input text.
	 */
	private void showSoftInput() {
		InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
		if (inputMethodManager != null) {
			if (mHasSelectorWheel) {
				mInputText.setVisibility(View.INVISIBLE);
			}
			mInputText.requestFocus();
			inputMethodManager.showSoftInput(mInputText, 0);
		}
	}

	/**
	 * Hides the soft input if it is active for the input text.
	 */
	private void hideSoftInput() {
		InputMethodManager inputMethodManager = InputMethodManager.peekInstance();
		if (inputMethodManager != null && inputMethodManager.isActive(mInputText)) {
			inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0);
			if (mHasSelectorWheel) {
				mInputText.setVisibility(View.INVISIBLE);
			}
		}
	}

	/**
	 * Computes the max width if no such specified as an attribute.
	 */
	private void tryComputeMaxWidth() {
		if (!mComputeMaxWidth) {
			return;
		}
		int maxTextWidth = 0;
		if (mDisplayedValues == null) {
			float maxDigitWidth = 0;
			for (int i = 0; i <= 9; i++) {
				final float digitWidth = mSelectorWheelPaint.measureText(formatNumberWithLocale(i));
				if (digitWidth > maxDigitWidth) {
					maxDigitWidth = digitWidth;
				}
			}
			int numberOfDigits = 0;
			int current = mMaxValue;
			while (current > 0) {
				numberOfDigits++;
				current = current / 10;
			}
			maxTextWidth = (int) (numberOfDigits * maxDigitWidth);
		} else {
			final int valueCount = mDisplayedValues.length;
			for (int i = 0; i < valueCount; i++) {
				final float textWidth = mSelectorWheelPaint.measureText(mDisplayedValues[i]);
				if (textWidth > maxTextWidth) {
					maxTextWidth = (int) textWidth;
				}
			}
		}
		maxTextWidth += mInputText.getPaddingLeft() + mInputText.getPaddingRight();
		if (mMaxWidth != maxTextWidth) {
			if (maxTextWidth > mMinWidth) {
				mMaxWidth = maxTextWidth;
			} else {
				mMaxWidth = mMinWidth;
			}
			invalidate();
		}
	}

	/**
	 * Gets whether the selector wheel wraps when reaching the min/max value.
	 * 
	 * @return True if the selector wheel wraps.
	 * 
	 * @see #getMinValue()
	 * @see #getMaxValue()
	 */
	public boolean getWrapSelectorWheel() {
		return mWrapSelectorWheel;
	}

	/**
	 * Sets whether the selector wheel shown during flinging/scrolling should
	 * wrap around the {@link NumberPicker#getMinValue()} and
	 * {@link NumberPicker#getMaxValue()} values.
	 * <p>
	 * By default if the range (max - min) is more than the number of items
	 * shown on the selector wheel the selector wheel wrapping is enabled.
	 * </p>
	 * <p>
	 * <strong>Note:</strong> If the number of items, i.e. the range (
	 * {@link #getMaxValue()} - {@link #getMinValue()}) is less than the number
	 * of items shown on the selector wheel, the selector wheel will not wrap.
	 * Hence, in such a case calling this method is a NOP.
	 * </p>
	 * 
	 * @param wrapSelectorWheel
	 *            Whether to wrap.
	 */
	public void setWrapSelectorWheel(boolean wrapSelectorWheel) {
		final boolean wrappingAllowed = (mMaxValue - mMinValue) >= mSelectorIndices.length;
		if ((!wrapSelectorWheel || wrappingAllowed) && wrapSelectorWheel != mWrapSelectorWheel) {
			mWrapSelectorWheel = wrapSelectorWheel;
		}
	}

	/**
	 * Sets the speed at which the numbers be incremented and decremented when
	 * the up and down buttons are long pressed respectively.
	 * <p>
	 * The default value is 300 ms.
	 * </p>
	 * 
	 * @param intervalMillis
	 *            The speed (in milliseconds) at which the numbers will be
	 *            incremented and decremented.
	 */
	public void setOnLongPressUpdateInterval(long intervalMillis) {
		mLongPressUpdateInterval = intervalMillis;
	}

	/**
	 * Returns the value of the picker.
	 * 
	 * @return The value.
	 */
	public int getValue() {
		return mValue;
	}

	/**
	 * Returns the min value of the picker.
	 * 
	 * @return The min value
	 */
	public int getMinValue() {
		return mMinValue;
	}

	/**
	 * Sets the min value of the picker.
	 * 
	 * @param minValue
	 *            The min value inclusive.
	 * 
	 *            <strong>Note:</strong> The length of the displayed values
	 *            array set via {@link #setDisplayedValues(String[])} must be
	 *            equal to the range of selectable numbers which is equal to
	 *            {@link #getMaxValue()} - {@link #getMinValue()} + 1.
	 */
	public void setMinValue(int minValue) {
		if (mMinValue == minValue) {
			return;
		}
		if (minValue < 0) {
			throw new IllegalArgumentException("minValue must be >= 0");
		}
		mMinValue = minValue;
		if (mMinValue > mValue) {
			mValue = mMinValue;
		}
		boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
		setWrapSelectorWheel(wrapSelectorWheel);
		initializeSelectorWheelIndices();
		updateInputTextView();
		tryComputeMaxWidth();
		invalidate();
	}

	/**
	 * Returns the max value of the picker.
	 * 
	 * @return The max value.
	 */
	public int getMaxValue() {
		return mMaxValue;
	}

	/**
	 * Sets the max value of the picker.
	 * 
	 * @param maxValue
	 *            The max value inclusive.
	 * 
	 *            <strong>Note:</strong> The length of the displayed values
	 *            array set via {@link #setDisplayedValues(String[])} must be
	 *            equal to the range of selectable numbers which is equal to
	 *            {@link #getMaxValue()} - {@link #getMinValue()} + 1.
	 */
	public void setMaxValue(int maxValue) {
		if (mMaxValue == maxValue) {
			return;
		}
		if (maxValue < 0) {
			throw new IllegalArgumentException("maxValue must be >= 0");
		}
		mMaxValue = maxValue;
		if (mMaxValue < mValue) {
			mValue = mMaxValue;
		}
		boolean wrapSelectorWheel = mMaxValue - mMinValue > mSelectorIndices.length;
		setWrapSelectorWheel(wrapSelectorWheel);
		initializeSelectorWheelIndices();
		updateInputTextView();
		tryComputeMaxWidth();
		invalidate();
	}

	/**
	 * Gets the values to be displayed instead of string values.
	 * 
	 * @return The displayed values.
	 */
	public String[] getDisplayedValues() {
		return mDisplayedValues;
	}

	/**
	 * Sets the values to be displayed.
	 * 
	 * @param displayedValues
	 *            The displayed values.
	 * 
	 *            <strong>Note:</strong> The length of the displayed values
	 *            array must be equal to the range of selectable numbers which
	 *            is equal to {@link #getMaxValue()} - {@link #getMinValue()} +
	 *            1.
	 */
	public void setDisplayedValues(String[] displayedValues) {
		if (mDisplayedValues == displayedValues) {
			return;
		}
		mDisplayedValues = displayedValues;
		if (mDisplayedValues != null) {
			// Allow text entry rather than strictly numeric entry.
			mInputText.setRawInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
		} else {
			mInputText.setRawInputType(InputType.TYPE_CLASS_NUMBER);
		}
		updateInputTextView();
		initializeSelectorWheelIndices();
		tryComputeMaxWidth();
	}

	@Override
	protected float getTopFadingEdgeStrength() {
		return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
	}

	@Override
	protected float getBottomFadingEdgeStrength() {
		return TOP_AND_BOTTOM_FADING_EDGE_STRENGTH;
	}

	@Override
	protected void onDetachedFromWindow() {
		super.onDetachedFromWindow();
		removeAllCallbacks();
	}

	@Override
	protected void onDraw(Canvas canvas) {
		if (!mHasSelectorWheel) {
			super.onDraw(canvas);
			return;
		}

		float x = (mRight - mLeft) / 2;
		switch (mAlignType) {
		case ALIGN_LEFT_TYPE:
			x = 0;
			break;
		case ALIGN_RIGHT_TYPE:
			x = mRight - mLeft;
			break;
		}
		float y = mCurrentScrollOffset;

		// draw the virtual buttons pressed state if needed
		if (mVirtualButtonPressedDrawable != null && mScrollState == OnScrollListener.SCROLL_STATE_IDLE) {
			if (mDecrementVirtualButtonPressed) {
				mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET);
				mVirtualButtonPressedDrawable.setBounds(0, 0, mRight, mTopSelectionDividerTop);
				mVirtualButtonPressedDrawable.draw(canvas);
			}
			if (mIncrementVirtualButtonPressed) {
				mVirtualButtonPressedDrawable.setState(PRESSED_STATE_SET);
				mVirtualButtonPressedDrawable.setBounds(0, mBottomSelectionDividerBottom, mRight, mBottom);
				mVirtualButtonPressedDrawable.draw(canvas);
			}
		}

		final int restoreCount = canvas.save();
		float currentY = mCurrentScrollOffset - mInitialScrollOffset + mSelectorElementHeight / 2;

		final int red = Color.red(mNormalTextColor);
		final int green = Color.green(mNormalTextColor);
		final int blue = Color.blue(mNormalTextColor);

		int[] selectorIndices = mSelectorIndices;
		for (int i = 0; i < selectorIndices.length; i++) {
			int selectorIndex = selectorIndices[i];
			String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex);
			float currentTextSize = mNormalTextSize;
			float clip = Math.abs(currentY - getHeight() / 2);
			float elementHeight = mSelectorElementHeight * 1.5f;
			float textScale = mTextScale - 1.0f;
			float scal = 0.0f;
			if (clip <= elementHeight) {
				scal = Math.abs(textScale - ((clip * textScale) / elementHeight));
				currentTextSize = mNormalTextSize * (scal + 1.0f);
			}

			mSelectorWheelPaint.setTextSize(currentTextSize);

			// The lowest transparency is 25
			int color = Color.argb((int) (255 * (0.25 + 0.75 * (scal / textScale))), red, green, blue);
			mSelectorWheelPaint.setColor(color);
			canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint);

			y += mSelectorElementHeight;
			currentY += mSelectorElementHeight;
		}

		// draw the selection dividers
		if (mSelectionDivider != null) {
			// draw the top divider
			int topOfTopDivider = mTopSelectionDividerTop;
			int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight;
			mSelectionDivider.setBounds(0, topOfTopDivider, mRight, bottomOfTopDivider);
			mSelectionDivider.draw(canvas);

			// draw the bottom divider
			int bottomOfBottomDivider = mBottomSelectionDividerBottom;
			int topOfBottomDivider = bottomOfBottomDivider - mSelectionDividerHeight;
			mSelectionDivider.setBounds(0, topOfBottomDivider, mRight, bottomOfBottomDivider);
			mSelectionDivider.draw(canvas);
		}
		if (mLabel != null) {
			int length = mInputText.getText().length();

			if (mLabelLocation == LABEL_RIGHT) {
				if (length > 3) {
					x = getWidth() / 2 + 45 * displayMetrics.density;
				} else {
					x = getWidth() / 2 + 32 * displayMetrics.density;
				}
			} else {
				if (length > 3) {
					x = getWidth() / 2 - 45 * displayMetrics.density;
				} else {
					x = getWidth() / 2 - 32 * displayMetrics.density;
				}
			}
			y = getHeight() / 2;
			canvas.drawText(mLabel, x, y, mLabelPaint);
		}
		canvas.restoreToCount(restoreCount);
	}

	@Override
	public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
		super.onInitializeAccessibilityEvent(event);
		event.setClassName(NumberPicker.class.getName());
		event.setScrollable(true);
		event.setScrollY((mMinValue + mValue) * mSelectorElementHeight);
		event.setMaxScrollY((mMaxValue - mMinValue) * mSelectorElementHeight);
	}

	@Override
	public AccessibilityNodeProvider getAccessibilityNodeProvider() {
		if (!mHasSelectorWheel) {
			return super.getAccessibilityNodeProvider();
		}
		if (mAccessibilityNodeProvider == null) {
			mAccessibilityNodeProvider = new AccessibilityNodeProviderImpl();
		}
		return mAccessibilityNodeProvider;
	}

	/**
	 * Makes a measure spec that tries greedily to use the max value.
	 * 
	 * @param measureSpec
	 *            The measure spec.
	 * @param maxSize
	 *            The max value for the size.
	 * @return A measure spec greedily imposing the max size.
	 */
	private int makeMeasureSpec(int measureSpec, int maxSize) {
		if (maxSize == SIZE_UNSPECIFIED) {
			return measureSpec;
		}
		final int size = MeasureSpec.getSize(measureSpec);
		final int mode = MeasureSpec.getMode(measureSpec);
		switch (mode) {
		case MeasureSpec.EXACTLY:
			return measureSpec;
		case MeasureSpec.AT_MOST:
			return MeasureSpec.makeMeasureSpec(Math.min(size, maxSize), MeasureSpec.EXACTLY);
		case MeasureSpec.UNSPECIFIED:
			return MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.EXACTLY);
		default:
			throw new IllegalArgumentException("Unknown measure mode: " + mode);
		}
	}

	/**
	 * Utility to reconcile a desired size and state, with constraints imposed
	 * by a MeasureSpec. Tries to respect the min size, unless a different size
	 * is imposed by the constraints.
	 * 
	 * @param minSize
	 *            The minimal desired size.
	 * @param measuredSize
	 *            The currently measured size.
	 * @param measureSpec
	 *            The current measure spec.
	 * @return The resolved size and state.
	 */
	private int resolveSizeAndStateRespectingMinSize(int minSize, int measuredSize, int measureSpec) {
		if (minSize != SIZE_UNSPECIFIED) {
			final int desiredWidth = Math.max(minSize, measuredSize);
			return resolveSizeAndState(desiredWidth, measureSpec, 0);
		} else {
			return measuredSize;
		}
	}

	/**
	 * Resets the selector indices and clear the cached string representation of
	 * these indices.
	 */
	private void initializeSelectorWheelIndices() {
		mSelectorIndexToStringCache.clear();
		int[] selectorIndices = mSelectorIndices;
		int current = getValue();
		for (int i = 0; i < mSelectorIndices.length; i++) {
			int selectorIndex = current + (i - SELECTOR_MIDDLE_ITEM_INDEX);
			if (mWrapSelectorWheel) {
				selectorIndex = getWrappedSelectorIndex(selectorIndex);
			}
			selectorIndices[i] = selectorIndex;
			ensureCachedScrollSelectorValue(selectorIndices[i]);
		}
	}

	/**
	 * Sets the current value of this NumberPicker.
	 * 
	 * @param current
	 *            The new value of the NumberPicker.
	 * @param notifyChange
	 *            Whether to notify if the current value changed.
	 */
	private void setValueInternal(int current, boolean notifyChange) {
		if (mValue == current) {
			return;
		}
		// Wrap around the values if we go past the start or end
		if (mWrapSelectorWheel) {
			current = getWrappedSelectorIndex(current);
		} else {
			current = Math.max(current, mMinValue);
			current = Math.min(current, mMaxValue);
		}
		int previous = mValue;
		mValue = current;
		updateInputTextView();
		if (notifyChange) {
			notifyChange(previous, current);
		}
		initializeSelectorWheelIndices();
		invalidate();
	}

	/**
	 * Changes the current value by one which is increment or decrement based on
	 * the passes argument. decrement the current value.
	 * 
	 * @param increment
	 *            True to increment, false to decrement.
	 */
	private void changeValueByOne(boolean increment) {
		if (!mChangeValueByOneEable) {
			return;
		}
		if (mHasSelectorWheel) {
			mInputText.setVisibility(View.INVISIBLE);
			if (!moveToFinalScrollerPosition(mFlingScroller)) {
				moveToFinalScrollerPosition(mAdjustScroller);
			}
			mPreviousScrollerY = 0;
			if (increment) {
				mFlingScroller.startScroll(0, 0, 0, -mSelectorElementHeight, SNAP_SCROLL_DURATION);
			} else {
				mFlingScroller.startScroll(0, 0, 0, mSelectorElementHeight, SNAP_SCROLL_DURATION);
			}
			invalidate();
		} else {
			if (increment) {
				setValueInternal(mValue + 1, true);
			} else {
				setValueInternal(mValue - 1, true);
			}
		}
	}

	private void changeValueByOffset(int selectorIndexOffset) {
		if (!mChangeValueByOneEable) {
			return;
		}

		if (mHasSelectorWheel) {
			mInputText.setVisibility(View.INVISIBLE);
			if (!moveToFinalScrollerPosition(mFlingScroller)) {
				moveToFinalScrollerPosition(mAdjustScroller);
			}
			mPreviousScrollerY = 0;
			mFlingScroller.startScroll(0, 0, 0, -mSelectorElementHeight * selectorIndexOffset, SNAP_SCROLL_DURATION);
			invalidate();
		} else {
			setValueInternal(mValue + selectorIndexOffset, true);
		}
	}

	private void initializeSelectorWheel() {
		initializeSelectorWheelIndices();
		int[] selectorIndices = mSelectorIndices;
		int totalTextHeight = selectorIndices.length * mTextSize;
		float totalTextGapHeight = (mBottom - mTop) - totalTextHeight;
		float textGapCount = selectorIndices.length;
		mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f);
		mSelectorElementHeight = mTextSize + mSelectorTextGapHeight;
		// Ensure that the middle item is positioned the same as the text in
		// mInputText
		int editTextTextPosition = mInputText.getBaseline() + mInputText.getTop();
		mInitialScrollOffset = editTextTextPosition - (mSelectorElementHeight * SELECTOR_MIDDLE_ITEM_INDEX);
		mCurrentScrollOffset = mInitialScrollOffset;
		updateInputTextView();
	}

	private void initializeFadingEdges() {
		// setVerticalFadingEdgeEnabled(true);
		// setFadingEdgeLength((mBottom - mTop - mTextSize) / 2);
	}

	/**
	 * Callback invoked upon completion of a given <code>scroller</code>.
	 */
	private void onScrollerFinished(TwsScroller scroller) {
		if (scroller == mFlingScroller) {
			if (!ensureScrollWheelAdjusted()) {
				updateInputTextView();
			}
			onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
		} else {
			if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
				updateInputTextView();
			}
		}
	}

	/**
	 * Handles transition to a given <code>scrollState</code>
	 */
	private void onScrollStateChange(int scrollState) {
		if (mScrollState == scrollState) {
			return;
		}
		mScrollState = scrollState;
		if (mOnScrollListener != null) {
			mOnScrollListener.onScrollStateChange(this, scrollState);
		}
	}

	/**
	 * Flings the selector with the given <code>velocityY</code>.
	 */
	private void fling(int velocityY) {
		mPreviousScrollerY = 0;

		if (!mWrapSelectorWheel && mSlowScroller) {
			if (Math.abs(velocityY) > 1000) {
				if (velocityY > 0) {
					velocityY = 1000;
				} else {
					velocityY = (-1000);
				}
			}
			if (velocityY > 0) {
				mFlingScroller.fling(0, 0, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE);
			} else {
				mFlingScroller.fling(0, Integer.MAX_VALUE, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE);
			}
		} else {
			if (velocityY > 0) {
				mFlingScroller.fling(0, 0, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE);
			} else {
				mFlingScroller.fling(0, Integer.MAX_VALUE, 0, velocityY, 0, 0, 0, Integer.MAX_VALUE);
			}
		}

		invalidate();
	}

	/**
	 * @return The wrapped index <code>selectorIndex</code> value.
	 */
	private int getWrappedSelectorIndex(int selectorIndex) {
		if (selectorIndex > mMaxValue) {
			return mMinValue + (selectorIndex - mMaxValue) % (mMaxValue - mMinValue) - 1;
		} else if (selectorIndex < mMinValue) {
			return mMaxValue - (mMinValue - selectorIndex) % (mMaxValue - mMinValue) + 1;
		}
		return selectorIndex;
	}

	/**
	 * Increments the <code>selectorIndices</code> whose string representations
	 * will be displayed in the selector.
	 */
	private void incrementSelectorIndices(int[] selectorIndices) {
		for (int i = 0; i < selectorIndices.length - 1; i++) {
			selectorIndices[i] = selectorIndices[i + 1];
		}
		int nextScrollSelectorIndex = selectorIndices[selectorIndices.length - 2] + 1;
		if (mWrapSelectorWheel && nextScrollSelectorIndex > mMaxValue) {
			nextScrollSelectorIndex = mMinValue;
		}
		selectorIndices[selectorIndices.length - 1] = nextScrollSelectorIndex;
		ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
	}

	/**
	 * Decrements the <code>selectorIndices</code> whose string representations
	 * will be displayed in the selector.
	 */
	private void decrementSelectorIndices(int[] selectorIndices) {
		for (int i = selectorIndices.length - 1; i > 0; i--) {
			selectorIndices[i] = selectorIndices[i - 1];
		}
		int nextScrollSelectorIndex = selectorIndices[1] - 1;
		if (mWrapSelectorWheel && nextScrollSelectorIndex < mMinValue) {
			nextScrollSelectorIndex = mMaxValue;
		}
		selectorIndices[0] = nextScrollSelectorIndex;
		ensureCachedScrollSelectorValue(nextScrollSelectorIndex);
	}

	/**
	 * Ensures we have a cached string representation of the given <code>
	 * selectorIndex</code> to avoid multiple instantiations of the same string.
	 */
	private void ensureCachedScrollSelectorValue(int selectorIndex) {
		SparseArray<String> cache = mSelectorIndexToStringCache;
		String scrollSelectorValue = cache.get(selectorIndex);
		if (scrollSelectorValue != null) {
			return;
		}
		if (selectorIndex < mMinValue || selectorIndex > mMaxValue) {
			scrollSelectorValue = "";
		} else {
			if (mDisplayedValues != null) {
				int displayedValueIndex = selectorIndex - mMinValue;
				scrollSelectorValue = mDisplayedValues[displayedValueIndex];
			} else {
				scrollSelectorValue = formatNumber(selectorIndex);
			}
		}
		cache.put(selectorIndex, scrollSelectorValue);
	}

	private String formatNumber(int value) {
		return (mFormatter != null) ? mFormatter.format(value) : formatNumberWithLocale(value);
	}

	private void validateInputTextView(View v) {
		String str = String.valueOf(((TextView) v).getText());
		if (TextUtils.isEmpty(str)) {
			// Restore to the old value as we don't allow empty values
			updateInputTextView();
		} else {
			// Check the new value and ensure it's in range
			int current = getSelectedPos(str.toString());
			setValueInternal(current, true);
		}
	}

	/**
	 * Updates the view of this NumberPicker. If displayValues were specified in
	 * the string corresponding to the index specified by the current value will
	 * be returned. Otherwise, the formatter specified in {@link #setFormatter}
	 * will be used to format the number.
	 * 
	 * @return Whether the text was updated.
	 */
	private boolean updateInputTextView() {
		/*
		 * If we don't have displayed values then use the current number else
		 * find the correct value in the displayed values for the current
		 * number.
		 */
		String text = (mDisplayedValues == null) ? formatNumber(mValue) : mDisplayedValues[mValue - mMinValue];
		if (!TextUtils.isEmpty(text) && !text.equals(mInputText.getText().toString())) {
			mInputText.setText(text);
			return true;
		}

		return false;
	}

	/**
	 * Notifies the listener, if registered, of a change of the value of this
	 * NumberPicker.
	 */
	private void notifyChange(int previous, int current) {
		if (mOnValueChangeListener != null) {
			mOnValueChangeListener.onValueChange(this, previous, mValue);
		}
	}

	/**
	 * Posts a command for changing the current value by one.
	 * 
	 * @param increment
	 *            Whether to increment or decrement the value.
	 */
	private void postChangeCurrentByOneFromLongPress(boolean increment, long delayMillis) {
		if (!mChangeCurrentByOneFromLongPressEable) {
			return;
		}
		if (mChangeCurrentByOneFromLongPressCommand == null) {
			mChangeCurrentByOneFromLongPressCommand = new ChangeCurrentByOneFromLongPressCommand();
		} else {
			removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
		}
		mChangeCurrentByOneFromLongPressCommand.setStep(increment);
		postDelayed(mChangeCurrentByOneFromLongPressCommand, delayMillis);
	}

	/**
	 * Removes the command for changing the current value by one.
	 */
	private void removeChangeCurrentByOneFromLongPress() {
		if (mChangeCurrentByOneFromLongPressCommand != null) {
			removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
		}
	}

	/**
	 * Posts a command for beginning an edit of the current value via IME on
	 * long press.
	 */
	private void postBeginSoftInputOnLongPressCommand() {
		if (mBeginSoftInputOnLongPressCommand == null) {
			mBeginSoftInputOnLongPressCommand = new BeginSoftInputOnLongPressCommand();
		} else {
			removeCallbacks(mBeginSoftInputOnLongPressCommand);
		}
		postDelayed(mBeginSoftInputOnLongPressCommand, ViewConfiguration.getLongPressTimeout());
	}

	/**
	 * Removes the command for beginning an edit of the current value via IME.
	 */
	private void removeBeginSoftInputCommand() {
		if (mBeginSoftInputOnLongPressCommand != null) {
			removeCallbacks(mBeginSoftInputOnLongPressCommand);
		}
	}

	/**
	 * Removes all pending callback from the message queue.
	 */
	private void removeAllCallbacks() {
		if (mChangeCurrentByOneFromLongPressCommand != null) {
			removeCallbacks(mChangeCurrentByOneFromLongPressCommand);
		}
		if (mSetSelectionCommand != null) {
			removeCallbacks(mSetSelectionCommand);
		}
		if (mBeginSoftInputOnLongPressCommand != null) {
			removeCallbacks(mBeginSoftInputOnLongPressCommand);
		}
		mPressedStateHelper.cancel();
	}

	/**
	 * @return The selected index given its displayed <code>value</code>.
	 */
	private int getSelectedPos(String value) {
		if (mDisplayedValues == null) {
			try {
				return Integer.parseInt(value);
			} catch (NumberFormatException e) {
				// Ignore as if it's not a number we don't care
			}
		} else {
			for (int i = 0; i < mDisplayedValues.length; i++) {
				// Don't force the user to type in jan when ja will do
				value = value.toLowerCase();
				if (mDisplayedValues[i].toLowerCase().startsWith(value)) {
					return mMinValue + i;
				}
			}

			/*
			 * The user might have typed in a number into the month field i.e.
			 * 10 instead of OCT so support that too.
			 */
			try {
				return Integer.parseInt(value);
			} catch (NumberFormatException e) {

				// Ignore as if it's not a number we don't care
			}
		}
		return mMinValue;
	}

	/**
	 * Posts an {@link SetSelectionCommand} from the given <code>selectionStart
	 * </code> to <code>selectionEnd</code>.
	 */
	private void postSetSelectionCommand(int selectionStart, int selectionEnd) {
		if (mSetSelectionCommand == null) {
			mSetSelectionCommand = new SetSelectionCommand();
		} else {
			removeCallbacks(mSetSelectionCommand);
		}
		mSetSelectionCommand.mSelectionStart = selectionStart;
		mSetSelectionCommand.mSelectionEnd = selectionEnd;
		post(mSetSelectionCommand);
	}

	/**
	 * The numbers accepted by the input text's {@link Filter}
	 */
	private static final char[] DIGIT_CHARACTERS = new char[] {
			// Latin digits are the common case
			'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
			// Arabic-Indic
			'\u0660', '\u0661', '\u0662', '\u0663', '\u0664', '\u0665', '\u0666', '\u0667', '\u0668', '\u0669',
			// Extended Arabic-Indic
			'\u06f0', '\u06f1', '\u06f2', '\u06f3', '\u06f4', '\u06f5', '\u06f6', '\u06f7', '\u06f8', '\u06f9' };

	/**
	 * Filter for accepting only valid indices or prefixes of the string
	 * representation of valid indices.
	 */
	class InputTextFilter extends NumberKeyListener {

		// XXX This doesn't allow for range limits when controlled by a
		// soft input method!
		public int getInputType() {
			return InputType.TYPE_CLASS_TEXT;
		}

		@Override
		protected char[] getAcceptedChars() {
			return DIGIT_CHARACTERS;
		}

		@Override
		public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
			if (mDisplayedValues == null) {
				CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
				if (filtered == null) {
					filtered = source.subSequence(start, end);
				}

				String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
						+ dest.subSequence(dend, dest.length());

				if ("".equals(result)) {
					return result;
				}
				int val = getSelectedPos(result);

				/*
				 * Ensure the user can't type in a value greater than the max
				 * allowed. We have to allow less than min as the user might
				 * want to delete some numbers and then type a new number. And
				 * prevent multiple-"0" that exceeds the length of upper bound
				 * number.
				 */
				if (val > mMaxValue || result.length() > String.valueOf(mMaxValue).length()) {
					return "";
				} else {
					return filtered;
				}
			} else {
				CharSequence filtered = String.valueOf(source.subSequence(start, end));
				if (TextUtils.isEmpty(filtered)) {
					return "";
				}
				String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
						+ dest.subSequence(dend, dest.length());
				String str = String.valueOf(result).toLowerCase();
				for (String val : mDisplayedValues) {
					String valLowerCase = val.toLowerCase();
					if (valLowerCase.startsWith(str)) {
						postSetSelectionCommand(result.length(), val.length());
						return val.subSequence(dstart, val.length());
					}
				}
				return "";
			}
		}
	}

	/**
	 * Ensures that the scroll wheel is adjusted i.e. there is no offset and the
	 * middle element is in the middle of the widget.
	 * 
	 * @return Whether an adjustment has been made.
	 */
	private boolean ensureScrollWheelAdjusted() {
		// adjust to the closest value
		int deltaY = mInitialScrollOffset - mCurrentScrollOffset;
		if (deltaY != 0) {
			mPreviousScrollerY = 0;
			if (Math.abs(deltaY) > mSelectorElementHeight / 2) {
				deltaY += (deltaY > 0) ? -mSelectorElementHeight : mSelectorElementHeight;
			}
			mAdjustScroller.startScroll(0, 0, 0, deltaY, SELECTOR_ADJUSTMENT_DURATION_MILLIS);
			invalidate();
			return true;
		}
		return false;
	}

	class PressedStateHelper implements Runnable {
		public static final int BUTTON_INCREMENT = 1;
		public static final int BUTTON_DECREMENT = 2;

		private final int MODE_PRESS = 1;
		private final int MODE_TAPPED = 2;

		private int mManagedButton;
		private int mMode;

		public void cancel() {
			mMode = 0;
			mManagedButton = 0;
			NumberPicker.this.removeCallbacks(this);
			if (mIncrementVirtualButtonPressed) {
				mIncrementVirtualButtonPressed = false;
				invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
			}
			mDecrementVirtualButtonPressed = false;
			if (mDecrementVirtualButtonPressed) {
				invalidate(0, 0, mRight, mTopSelectionDividerTop);
			}
		}

		public void buttonPressDelayed(int button) {
			cancel();
			mMode = MODE_PRESS;
			mManagedButton = button;
			NumberPicker.this.postDelayed(this, ViewConfiguration.getTapTimeout());
		}

		public void buttonTapped(int button) {
			cancel();
			mMode = MODE_TAPPED;
			mManagedButton = button;
			NumberPicker.this.post(this);
		}

		@Override
		public void run() {
			switch (mMode) {
			case MODE_PRESS: {
				switch (mManagedButton) {
				case BUTTON_INCREMENT: {
					mIncrementVirtualButtonPressed = true;
					invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
				}
					break;
				case BUTTON_DECREMENT: {
					mDecrementVirtualButtonPressed = true;
					invalidate(0, 0, mRight, mTopSelectionDividerTop);
				}
				}
			}
				break;
			case MODE_TAPPED: {
				switch (mManagedButton) {
				case BUTTON_INCREMENT: {
					if (!mIncrementVirtualButtonPressed) {
						NumberPicker.this.postDelayed(this, ViewConfiguration.getPressedStateDuration());
					}
					mIncrementVirtualButtonPressed ^= true;
					invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
				}
					break;
				case BUTTON_DECREMENT: {
					if (!mDecrementVirtualButtonPressed) {
						NumberPicker.this.postDelayed(this, ViewConfiguration.getPressedStateDuration());
					}
					mDecrementVirtualButtonPressed ^= true;
					invalidate(0, 0, mRight, mTopSelectionDividerTop);
				}
				}
			}
				break;
			}
		}
	}

	/**
	 * Command for setting the input text selection.
	 */
	class SetSelectionCommand implements Runnable {
		private int mSelectionStart;

		private int mSelectionEnd;

		public void run() {
			// mInputText.setSelection(mSelectionStart, mSelectionEnd);
		}
	}

	/**
	 * Command for changing the current value from a long press by one.
	 */
	class ChangeCurrentByOneFromLongPressCommand implements Runnable {
		private boolean mIncrement;

		private void setStep(boolean increment) {
			mIncrement = increment;
		}

		@Override
		public void run() {
			changeValueByOne(mIncrement);
			// postDelayed(this, mLongPressUpdateInterval);
		}
	}

	/**
	 * @hide
	 */
	public static class CustomEditText extends EditText {

		public CustomEditText(Context context, AttributeSet attrs) {
			super(context, attrs);
		}

		@Override
		public void onEditorAction(int actionCode) {
			super.onEditorAction(actionCode);
			if (actionCode == EditorInfo.IME_ACTION_DONE) {
				clearFocus();
			}
		}
	}

	/**
	 * Command for beginning soft input on long press.
	 */
	class BeginSoftInputOnLongPressCommand implements Runnable {

		@Override
		public void run() {
			performLongClick();
		}
	}

	/**
	 * Class for managing virtual view tree rooted at this picker.
	 */
	class AccessibilityNodeProviderImpl extends AccessibilityNodeProvider {
		private static final int UNDEFINED = Integer.MIN_VALUE;

		private static final int VIRTUAL_VIEW_ID_INCREMENT = 1;

		private static final int VIRTUAL_VIEW_ID_INPUT = 2;

		private static final int VIRTUAL_VIEW_ID_DECREMENT = 3;

		private final Rect mTempRect = new Rect();

		private final int[] mTempArray = new int[2];

		private int mAccessibilityFocusedView = UNDEFINED;

		@Override
		public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
			switch (virtualViewId) {
			case View.NO_ID:
				return createAccessibilityNodeInfoForNumberPicker(mScrollX, mScrollY, mScrollX + (mRight - mLeft),
						mScrollY + (mBottom - mTop));
			case VIRTUAL_VIEW_ID_DECREMENT:
				return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_DECREMENT,
						getVirtualDecrementButtonText(), mScrollX, mScrollY, mScrollX + (mRight - mLeft),
						mTopSelectionDividerTop + mSelectionDividerHeight);
			case VIRTUAL_VIEW_ID_INPUT:
				return createAccessibiltyNodeInfoForInputText(mScrollX, mTopSelectionDividerTop
						+ mSelectionDividerHeight, mScrollX + (mRight - mLeft), mBottomSelectionDividerBottom
						- mSelectionDividerHeight);
			case VIRTUAL_VIEW_ID_INCREMENT:
				return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_INCREMENT,
						getVirtualIncrementButtonText(), mScrollX, mBottomSelectionDividerBottom
								- mSelectionDividerHeight, mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop));
			}
			return super.createAccessibilityNodeInfo(virtualViewId);
		}

		@Override
		public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String searched, int virtualViewId) {
			if (TextUtils.isEmpty(searched)) {
				return Collections.emptyList();
			}
			String searchedLowerCase = searched.toLowerCase();
			List<AccessibilityNodeInfo> result = new ArrayList<AccessibilityNodeInfo>();
			switch (virtualViewId) {
			case View.NO_ID: {
				findAccessibilityNodeInfosByTextInChild(searchedLowerCase, VIRTUAL_VIEW_ID_DECREMENT, result);
				findAccessibilityNodeInfosByTextInChild(searchedLowerCase, VIRTUAL_VIEW_ID_INPUT, result);
				findAccessibilityNodeInfosByTextInChild(searchedLowerCase, VIRTUAL_VIEW_ID_INCREMENT, result);
				return result;
			}
			case VIRTUAL_VIEW_ID_DECREMENT:
			case VIRTUAL_VIEW_ID_INCREMENT:
			case VIRTUAL_VIEW_ID_INPUT: {
				findAccessibilityNodeInfosByTextInChild(searchedLowerCase, virtualViewId, result);
				return result;
			}
			}
			return super.findAccessibilityNodeInfosByText(searched, virtualViewId);
		}

		@Override
		public boolean performAction(int virtualViewId, int action, Bundle arguments) {
			switch (virtualViewId) {
			case View.NO_ID: {
				switch (action) {
				case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
					if (mAccessibilityFocusedView != virtualViewId) {
						mAccessibilityFocusedView = virtualViewId;
						requestAccessibilityFocus();
						return true;
					}
				}
					return false;
				case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
					if (mAccessibilityFocusedView == virtualViewId) {
						mAccessibilityFocusedView = UNDEFINED;
						clearAccessibilityFocus();
						return true;
					}
					return false;
				}
				case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
					if (NumberPicker.this.isEnabled() && (getWrapSelectorWheel() || getValue() < getMaxValue())) {
						changeValueByOne(true);
						return true;
					}
				}
					return false;
				case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
					if (NumberPicker.this.isEnabled() && (getWrapSelectorWheel() || getValue() > getMinValue())) {
						changeValueByOne(false);
						return true;
					}
				}
					return false;
				}
			}
				break;
			case VIRTUAL_VIEW_ID_INPUT: {
				switch (action) {
				case AccessibilityNodeInfo.ACTION_FOCUS: {
					if (NumberPicker.this.isEnabled() && !mInputText.isFocused()) {
						return mInputText.requestFocus();
					}
				}
					break;
				case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: {
					if (NumberPicker.this.isEnabled() && mInputText.isFocused()) {
						mInputText.clearFocus();
						return true;
					}
					return false;
				}
				case AccessibilityNodeInfo.ACTION_CLICK: {
					if (NumberPicker.this.isEnabled()) {
						performClick();
						return true;
					}
					return false;
				}
				case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
					if (NumberPicker.this.isEnabled()) {
						performLongClick();
						return true;
					}
					return false;
				}
				case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
					if (mAccessibilityFocusedView != virtualViewId) {
						mAccessibilityFocusedView = virtualViewId;
						sendAccessibilityEventForVirtualView(virtualViewId,
								AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
						mInputText.invalidate();
						return true;
					}
				}
					return false;
				case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
					if (mAccessibilityFocusedView == virtualViewId) {
						mAccessibilityFocusedView = UNDEFINED;
						sendAccessibilityEventForVirtualView(virtualViewId,
								AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
						mInputText.invalidate();
						return true;
					}
				}
					return false;
				default: {
					return mInputText.performAccessibilityAction(action, arguments);
				}
				}
			}
				return false;
			case VIRTUAL_VIEW_ID_INCREMENT: {
				switch (action) {
				case AccessibilityNodeInfo.ACTION_CLICK: {
					if (NumberPicker.this.isEnabled()) {
						NumberPicker.this.changeValueByOne(true);
						sendAccessibilityEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_CLICKED);
						return true;
					}
				}
					return false;
				case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
					if (mAccessibilityFocusedView != virtualViewId) {
						mAccessibilityFocusedView = virtualViewId;
						sendAccessibilityEventForVirtualView(virtualViewId,
								AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
						invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
						return true;
					}
				}
					return false;
				case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
					if (mAccessibilityFocusedView == virtualViewId) {
						mAccessibilityFocusedView = UNDEFINED;
						sendAccessibilityEventForVirtualView(virtualViewId,
								AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
						invalidate(0, mBottomSelectionDividerBottom, mRight, mBottom);
						return true;
					}
				}
					return false;
				}
			}
				return false;
			case VIRTUAL_VIEW_ID_DECREMENT: {
				switch (action) {
				case AccessibilityNodeInfo.ACTION_CLICK: {
					if (NumberPicker.this.isEnabled()) {
						final boolean increment = (virtualViewId == VIRTUAL_VIEW_ID_INCREMENT);
						NumberPicker.this.changeValueByOne(increment);
						sendAccessibilityEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_CLICKED);
						return true;
					}
				}
					return false;
				case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
					if (mAccessibilityFocusedView != virtualViewId) {
						mAccessibilityFocusedView = virtualViewId;
						sendAccessibilityEventForVirtualView(virtualViewId,
								AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
						invalidate(0, 0, mRight, mTopSelectionDividerTop);
						return true;
					}
				}
					return false;
				case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
					if (mAccessibilityFocusedView == virtualViewId) {
						mAccessibilityFocusedView = UNDEFINED;
						sendAccessibilityEventForVirtualView(virtualViewId,
								AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
						invalidate(0, 0, mRight, mTopSelectionDividerTop);
						return true;
					}
				}
					return false;
				}
			}
				return false;
			}
			return super.performAction(virtualViewId, action, arguments);
		}

		public void sendAccessibilityEventForVirtualView(int virtualViewId, int eventType) {
			switch (virtualViewId) {
			case VIRTUAL_VIEW_ID_DECREMENT: {
				if (hasVirtualDecrementButton()) {
					sendAccessibilityEventForVirtualButton(virtualViewId, eventType, getVirtualDecrementButtonText());
				}
			}
				break;
			case VIRTUAL_VIEW_ID_INPUT: {
				sendAccessibilityEventForVirtualText(eventType);
			}
				break;
			case VIRTUAL_VIEW_ID_INCREMENT: {
				if (hasVirtualIncrementButton()) {
					sendAccessibilityEventForVirtualButton(virtualViewId, eventType, getVirtualIncrementButtonText());
				}
			}
				break;
			}
		}

		private void sendAccessibilityEventForVirtualText(int eventType) {
			if (AccessibilityManager.getInstance(mContext).isEnabled()) {
				AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
				mInputText.onInitializeAccessibilityEvent(event);
				mInputText.onPopulateAccessibilityEvent(event);
				event.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
				requestSendAccessibilityEvent(NumberPicker.this, event);
			}
		}

		private void sendAccessibilityEventForVirtualButton(int virtualViewId, int eventType, String text) {
			if (AccessibilityManager.getInstance(mContext).isEnabled()) {
				AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
				event.setClassName(Button.class.getName());
				event.setPackageName(mContext.getPackageName());
				event.getText().add(text);
				event.setEnabled(NumberPicker.this.isEnabled());
				event.setSource(NumberPicker.this, virtualViewId);
				requestSendAccessibilityEvent(NumberPicker.this, event);
			}
		}

		private void findAccessibilityNodeInfosByTextInChild(String searchedLowerCase, int virtualViewId,
				List<AccessibilityNodeInfo> outResult) {
			switch (virtualViewId) {
			case VIRTUAL_VIEW_ID_DECREMENT: {
				String text = getVirtualDecrementButtonText();
				if (!TextUtils.isEmpty(text) && text.toString().toLowerCase().contains(searchedLowerCase)) {
					outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT));
				}
			}
				return;
			case VIRTUAL_VIEW_ID_INPUT: {
				CharSequence text = mInputText.getText();
				if (!TextUtils.isEmpty(text) && text.toString().toLowerCase().contains(searchedLowerCase)) {
					outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT));
					return;
				}
				CharSequence contentDesc = mInputText.getText();
				if (!TextUtils.isEmpty(contentDesc) && contentDesc.toString().toLowerCase().contains(searchedLowerCase)) {
					outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT));
					return;
				}
			}
				break;
			case VIRTUAL_VIEW_ID_INCREMENT: {
				String text = getVirtualIncrementButtonText();
				if (!TextUtils.isEmpty(text) && text.toString().toLowerCase().contains(searchedLowerCase)) {
					outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT));
				}
			}
				return;
			}
		}

		private AccessibilityNodeInfo createAccessibiltyNodeInfoForInputText(int left, int top, int right, int bottom) {
			AccessibilityNodeInfo info = mInputText.createAccessibilityNodeInfo();
			info.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
			if (mAccessibilityFocusedView != VIRTUAL_VIEW_ID_INPUT) {
				info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
			}
			if (mAccessibilityFocusedView == VIRTUAL_VIEW_ID_INPUT) {
				info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
			}
			Rect boundsInParent = mTempRect;
			boundsInParent.set(left, top, right, bottom);
			info.setVisibleToUser(isVisibleToUser(boundsInParent));
			info.setBoundsInParent(boundsInParent);
			Rect boundsInScreen = boundsInParent;
			int[] locationOnScreen = mTempArray;
			getLocationOnScreen(locationOnScreen);
			boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
			info.setBoundsInScreen(boundsInScreen);
			return info;
		}

		private AccessibilityNodeInfo createAccessibilityNodeInfoForVirtualButton(int virtualViewId, String text,
				int left, int top, int right, int bottom) {
			AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
			info.setClassName(Button.class.getName());
			info.setPackageName(mContext.getPackageName());
			info.setSource(NumberPicker.this, virtualViewId);
			info.setParent(NumberPicker.this);
			info.setText(text);
			info.setClickable(true);
			info.setLongClickable(true);
			info.setEnabled(NumberPicker.this.isEnabled());
			Rect boundsInParent = mTempRect;
			boundsInParent.set(left, top, right, bottom);
			info.setVisibleToUser(isVisibleToUser(boundsInParent));
			info.setBoundsInParent(boundsInParent);
			Rect boundsInScreen = boundsInParent;
			int[] locationOnScreen = mTempArray;
			getLocationOnScreen(locationOnScreen);
			boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
			info.setBoundsInScreen(boundsInScreen);

			if (mAccessibilityFocusedView != virtualViewId) {
				info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
			}
			if (mAccessibilityFocusedView == virtualViewId) {
				info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
			}
			if (NumberPicker.this.isEnabled()) {
				info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
			}

			return info;
		}

		private AccessibilityNodeInfo createAccessibilityNodeInfoForNumberPicker(int left, int top, int right,
				int bottom) {
			AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
			info.setClassName(NumberPicker.class.getName());
			info.setPackageName(mContext.getPackageName());
			info.setSource(NumberPicker.this);

			if (hasVirtualDecrementButton()) {
				info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_DECREMENT);
			}
			info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
			if (hasVirtualIncrementButton()) {
				info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INCREMENT);
			}

			info.setParent((View) getParentForAccessibility());
			info.setEnabled(NumberPicker.this.isEnabled());
			info.setScrollable(true);

			final float applicationScale = getContext().getResources().getCompatibilityInfo().applicationScale;

			Rect boundsInParent = mTempRect;
			boundsInParent.set(left, top, right, bottom);
			boundsInParent.scale(applicationScale);
			info.setBoundsInParent(boundsInParent);

			info.setVisibleToUser(isVisibleToUser());

			Rect boundsInScreen = boundsInParent;
			int[] locationOnScreen = mTempArray;
			getLocationOnScreen(locationOnScreen);
			boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]);
			boundsInScreen.scale(applicationScale);
			info.setBoundsInScreen(boundsInScreen);

			if (mAccessibilityFocusedView != View.NO_ID) {
				info.addAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
			}
			if (mAccessibilityFocusedView == View.NO_ID) {
				info.addAction(AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
			}
			if (NumberPicker.this.isEnabled()) {
				if (getWrapSelectorWheel() || getValue() < getMaxValue()) {
					info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
				}
				if (getWrapSelectorWheel() || getValue() > getMinValue()) {
					info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
				}
			}

			return info;
		}

		private boolean hasVirtualDecrementButton() {
			return getWrapSelectorWheel() || getValue() > getMinValue();
		}

		private boolean hasVirtualIncrementButton() {
			return getWrapSelectorWheel() || getValue() < getMaxValue();
		}

		private String getVirtualDecrementButtonText() {
			int value = mValue - 1;
			if (mWrapSelectorWheel) {
				value = getWrappedSelectorIndex(value);
			}
			if (value >= mMinValue) {
				return (mDisplayedValues == null) ? formatNumber(value) : mDisplayedValues[value - mMinValue];
			}
			return null;
		}

		private String getVirtualIncrementButtonText() {
			int value = mValue + 1;
			if (mWrapSelectorWheel) {
				value = getWrappedSelectorIndex(value);
			}
			if (value <= mMaxValue) {
				return (mDisplayedValues == null) ? formatNumber(value) : mDisplayedValues[value - mMinValue];
			}
			return null;
		}
	}

	static private String formatNumberWithLocale(int value) {
		return String.format(Locale.getDefault(), "%d", value);
	}

	// tws-start label::2014-8-6
	/**
	 * Filter for accepting only valid indices or prefixes of the string
	 * representation of valid indices.
	 */
	class TwsInputTextFilter extends NumberKeyListener {

		// XXX This doesn't allow for range limits when controlled by a
		// soft input method!
		public int getInputType() {
			return InputType.TYPE_CLASS_TEXT;
		}

		@Override
		protected char[] getAcceptedChars() {
			return DIGIT_CHARACTERS;
		}

		@Override
		public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
			if (mDisplayedValues == null) {
				CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
				if (filtered == null) {
					filtered = source.subSequence(start, end);
				}

				String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
						+ dest.subSequence(dend, dest.length());

				if ("".equals(result)) {
					return result;
				}
				int val = getSelectedPos(result);

				/*
				 * Ensure the user can't type in a value greater than the max
				 * allowed. We have to allow less than min as the user might
				 * want to delete some numbers and then type a new number.
				 */
				/* tws-start::add for too many zeros */
				if (val > mMaxValue || (result.length() > Integer.toString(mMaxValue).length() && val == 0)) {
					return "";
				} else {
					return filtered;
				}
			} else {
				CharSequence filtered = String.valueOf(source.subSequence(start, end));
				if (TextUtils.isEmpty(filtered)) {
					return "";
				}
				String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
						+ dest.subSequence(dend, dest.length());
				String str = String.valueOf(result).toLowerCase();
				for (String val : mDisplayedValues) {
					String valLowerCase = val.toLowerCase();
					if (valLowerCase.startsWith(str)) {
						postSetSelectionCommand(result.length(), val.length());
						return val.subSequence(dstart, val.length());
					}
				}
				return "";
			}
		}
	}

	public void setLabel(String label) {
		setLabel(label, LABEL_RIGHT);
	}

	public void setLabel(String label, int location) {
		mLabel = label;
		mLabelLocation = location;
	}

	public void setChangeValueByOneEable(boolean eable) {
		mChangeValueByOneEable = eable;
	}

	public boolean getChangeValueByOneEable() {
		return mChangeValueByOneEable;
	}

	public void setChangeValueByOneFromLongPressEable(boolean eable) {
		mChangeCurrentByOneFromLongPressEable = eable;
	}

	public boolean getChangeValueByOneFromLongPressEable() {
		return mChangeCurrentByOneFromLongPressEable;
	}

	public void setInputTextFilter(InputFilter[] inputTextFilter) {
		mInputText.setFilters(inputTextFilter);
	}

	public void updateSelectorWheelIndices() {
		initializeSelectorWheelIndices();
	}

	public void setInputTextSize(float textSize) {
		mInputText.setTextSize(textSize);
		mTextScale = mInputText.getTextSize() / mNormalTextSize;
		invalidate();
	}

	public void setInputTextSize(int flag, float textSize) {
		mInputText.setTextSize(flag, textSize);
		mTextScale = mInputText.getTextSize() / mNormalTextSize;
		invalidate();
	}

	// tws-end label::2014-8-6

	public void setSlowScroller(boolean slowScroller) {
		mSlowScroller = slowScroller;
	}
}
